let fs=require('fs')
let path=require('path')
let babylon = require('babylon');
let t = require('@babel/types');
let traverse = require('@babel/traverse').default;
let generator = require('@babel/generator').default;
let ejs=require('ejs')
let {SyncHook}=require('tapable')
//  babylon 把源码转换成AST
//@babel/traverse 遍历节点
//@babel/types 替换
//@babel/generator 把替换好的结果生成 
class Compiler{
    constructor(config){
        this.config=config
        // 主模块路径
        this.entryId
        // 存放路径对应的代码的依赖关系
        this.modules={}
        // 入口路径名
        this.entry=config.entry
        // 工作目录是相对路径，需要找到对应的绝对路径
        this.root=process.cwd()
        this.hooks = { // webpack的生命周期（在合适的时候去调用这些函数）
            entryOption:new SyncHook(), //入口的时候
            compile:new SyncHook(), // 编译的时候
            afterCompile:new SyncHook(), //编译之后
            afterPulgins:new SyncHook(), //到编译完插件以后执行这个钩子
            run:new SyncHook(), //运行的时候
            emit:new SyncHook(),//发射文件的的时候
            done:new SyncHook()//完成的时候
          }
          // 如果传递了plugins参数
          let plugins = this.config.plugins;
          if(Array.isArray(plugins)){
            plugins.forEach(plugin => {
              plugin.apply(this);
            });
          }
          this.hooks.afterPulgins.call();
    }
    getSource(modulePath){
        let content=fs.readFileSync(modulePath,'utf8')
        let rules=this.config.module.rules
        for(let i=0;i<rules.length;i++){
            let {test,use}=rules[i]
            let len=use.length-1
            if(test.test(modulePath)){
                function nomarlLoader(){
                    let loader=require(use[len--])
                    content=loader(content)
                    // console.log(content)
                    if(len>=0){
                        nomarlLoader()
                    }
                }
                nomarlLoader()
            }
        }
        return content
    }
    parse(source,parentPath){// AST解析语法树
        // console.log(source,parentPath)
        let ast=babylon.parse(source) //把源码转换成AST语法树
        let dependencies=[]
        //遍历
        traverse(ast,{
            CallExpression(p) { //  调用表达式a() require()
                let node = p.node; // 对应的节点
                if (node.callee.name === 'require') {
                  node.callee.name = '__webpack_require__';//把require改成__webpack_require__
                  let moduleName = node.arguments[0].value; // 取到是就是require里面的路径
                  moduleName = moduleName + (path.extname(moduleName) ? '' : '.js'); //给路径加上后缀
                  moduleName = './' + path.join(parentPath, moduleName); 'src/a.js'//给路径加上父路径
                  dependencies.push(moduleName); //一个文件可能会有很多依赖
                  node.arguments = [t.stringLiteral(moduleName)];//改源码
                }
              }
        })
       let sourceCode = generator(ast).code// 源码改掉以后重新生成一下
       return {dependencies,sourceCode}
    }
    // 构建模块
    buildModule(modulePath,isEntry){// 传入两个参数入口文件的路径，是否是入口
        let source=this.getSource(modulePath) // 拿到路径对应的内容
        let moduleName='./'+path.relative(this.root,modulePath)//relative可以获取到相对路径
        if(isEntry){
            this.entryId=moduleName//如果是入口文件的话把相对路径名保存起来
        }
        // console.log(source,moduleName)
        // 把source源码进行改造返回改造后的源码和一个依赖列表
        let {sourceCode,dependencies}=this.parse(source,path.dirname(moduleName))//path.dirname(moduleName)是获取路径中的父路径
        // console.log(sourceCode,dependencies)
        // 把路径和路径文件中的内容对应起来
        this.modules[moduleName]=sourceCode
        //递归所有依赖
        dependencies.forEach(dep=>{
            this.buildModule(path.join(this.root,dep),false)
        })
    }
    emitFile(){
        // 获取输出路径
        let main=path.join(this.config.output.path,this.config.output.filename)
        // 读取模版
        let templateStr=this.getSource(path.join(__dirname,'main.ejs'))
        // 通过模版读取文件内容
        let code=ejs.render(templateStr,{entryId:this.entryId,modules:this.modules})
        // 把读取的内容写对应的文件中
        this.assets = {}
        this.assets[main] = code;
        fs.writeFileSync(main,this.assets[main])
    }
    run(){
        this.hooks.run.call()  
        this.hooks.compile.call()        
        // 执行并且创建模块的依赖关系
        this.buildModule(path.resolve(this.root,this.entry),true)
        //console.log(this.modules,this.entryId)
        this.hooks.afterCompile.call()        
        // 发射一个文件 打包后的文件
        this.emitFile()
        this.hooks.emit.call()
        this.hooks.done.call()
        
    }
}
module.exports=Compiler